/*
 * Copyright 2020-2030 ALPHA LAB.Inc All Rights Reserved.
 */
package com.alphalab.framework.log;

import java.io.File;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Layout;
import org.apache.log4j.Logger;
import org.apache.log4j.spi.LoggingEvent;

/**
 * 拓展log4j按日期生成日志的处理程序.新增日志文件保留天数属性maxBackupIndex.
 * @author allen
 * @version 1.0.0 2020年2月24日
 */
public class MoDailyRollingFileAppender extends FileAppender {

	private static final Logger logger = Logger.getLogger(MoDailyRollingFileAppender.class);
	/**
	 * 日志文件保留天数.包括当天.
	 */
	private int maxBackupIndex  = 60;
	static final int TOP_OF_TROUBLE = -1;
	static final int TOP_OF_MINUTE = 0;
	static final int TOP_OF_HOUR = 1;
	static final int HALF_DAY = 2;
	static final int TOP_OF_DAY = 3;
	static final int TOP_OF_WEEK = 4;
	static final int TOP_OF_MONTH = 5;
	private String datePattern = "'.'yyyy-MM-dd";
	private String scheduledFilename;
	private long nextCheck = System.currentTimeMillis() - 1L;
	Date now = new Date();
	SimpleDateFormat sdf;
	RollingCalendar rc = new RollingCalendar();
	int checkPeriod = -1;
	static final TimeZone GMT_TIME_ZONE = TimeZone.getTimeZone("GMT");

	public MoDailyRollingFileAppender() {
	}

	public MoDailyRollingFileAppender(Layout layout, String filename, String datePattern) 
			throws IOException {
		super(layout, filename, true);
		this.datePattern = datePattern;
		activateOptions();
	}

	public void setDatePattern(String pattern) {
		this.datePattern = pattern;
	}

	public String getDatePattern() {
		return this.datePattern;
	}

	/**
	 * 读取log4j配置文件参数.
	 */
	@Override
	public void activateOptions() {
		super.activateOptions();
		if ((this.datePattern != null) && (this.fileName != null)) {
			this.now.setTime(System.currentTimeMillis());
			this.sdf = new SimpleDateFormat(this.datePattern);
			int type = computeCheckPeriod();
			printPeriodicity(type);
			this.rc.setType(type);
			File file = new File(this.fileName);
			// 文件最后一次修改日期
			this.scheduledFilename = (this.fileName + this.sdf.format(
					new Date(file.lastModified())));
		} else {
			logger.error("Either File or DatePattern options are not set for appender "
					+ "[" + this.name + "].");
		}
	}

	void printPeriodicity(int type) {
		switch (type) {
		case 0:
			logger.debug("Appender [" + this.name + "] to be rolled every minute.");
			break;
		case 1:
			logger.debug("Appender [" + this.name + "] to be rolled on top of every hour.");

			break;
		case 2:
			logger.debug("Appender [" + this.name + "] to be rolled at midday and midnight.");

			break;
		case 3:
			logger.debug("Appender [" + this.name + "] to be rolled at midnight.");

			break;
		case 4:
			logger.debug("Appender [" + this.name + "] to be rolled at start of week.");

			break;
		case 5:
			logger.debug("Appender [" + this.name + "] to be rolled at start of every month.");

			break;
		default:
			logger.warn("Unknown periodicity for appender [" + this.name + "].");
		}
	}

	int computeCheckPeriod() {
		RollingCalendar rollingCalendar = new RollingCalendar(GMT_TIME_ZONE, 
				Locale.getDefault());
		Date epoch = new Date(0L);
		int times = 5;
		if (this.datePattern != null) {
			for (int i = 0; i <= times; i++) {
				SimpleDateFormat simpleDateFormat = new SimpleDateFormat(this.datePattern);
				simpleDateFormat.setTimeZone(GMT_TIME_ZONE);
				String r0 = simpleDateFormat.format(epoch);
				rollingCalendar.setType(i);
				Date next = new Date(rollingCalendar.getNextCheckMillis(epoch));
				String r1 = simpleDateFormat.format(next);
				if ((r0 != null) && (r1 != null) && (!r0.equals(r1))) {
					return i;
				}
			}
		}
		return -1;
	}

	/**
	 * 日志文件生成.
	 * @throws IOException
	 */
	void rollOver() throws IOException {
		if (this.datePattern == null) {
			this.errorHandler.error("Missing DatePattern option in rollOver().");
			return;
		}
		String datedFilename = this.fileName + this.sdf.format(now);
		logger.debug("scheduledFilename=" + scheduledFilename + " and datedFilename=" +
				datedFilename);
		// 如果文件最后一次修改时间为当天，则不做任何处理
		if (this.scheduledFilename.equals(datedFilename)) {
			return;
		}
		closeFile();
		File target = new File(this.scheduledFilename);
		if (target.exists()) {
			target.delete();
		}
		File file = new File(this.fileName);
		// 将当前文件改名为以文件最后修改日期为后缀的文件名
		boolean result = file.renameTo(target);
		if (result) {
			logger.debug(this.fileName + " renameto -> " + this.scheduledFilename + "success");
		} else {
			logger.debug("Failed to rename [" + this.fileName + "] to [" 
		+ this.scheduledFilename + "].");
		}
		try {
			setFile(this.fileName, true, this.bufferedIO, this.bufferSize);
		} catch (IOException e) {
			this.errorHandler.error("setFile(" + this.fileName + ", true) call failed.");
		}
		this.scheduledFilename = datedFilename;
		File fileEx = new File(this.fileName);
		logger.debug("maxBackupIndex=" + maxBackupIndex);
		if (this.maxBackupIndex < 1) {
			return;
		}
		File folder = new File(fileEx.getParent());
		// 获取可以保留下来的文件的日期后缀范围
		List<String> maxBackupIndexDates = getMaxBackupIndexDates();
		logger.debug("maxBackupIndexDates=" + maxBackupIndexDates);
		logger.debug("fileName=" + this.fileName);
		File[] arrayOfFile;
		int j = (arrayOfFile = folder.listFiles()).length;
		for (int i = 0; i < j; i++) {
			File ff = arrayOfFile[i];
			// 对以日期结尾的、属于同类日志类别的、不在可保留日期范围内的日志文件进行删除
			if (!ff.getName().endsWith(".log") && ff.getName().startsWith(fileEx.getName()) ) {
				boolean checkResult = true;
	            for (String dateStr : maxBackupIndexDates) {
	              if (ff.getName().contains(dateStr)) {
	                  checkResult = false;
	                  continue;
	              }
	            }
	            if (checkResult) {
					if (ff.delete()) {
						logger.debug(ff.getName() + " deleted success!");
					} else {
						logger.error("Failed to deleted old DayRollingFileAppender file :" + 
					ff.getName());
					}	            	
	            }
			}
		}
	}

	/**
	 * 日志文件生成执行.
	 */
	@Override
	protected void subAppend(LoggingEvent event) {
		long n = System.currentTimeMillis();
		if (n >= this.nextCheck) {
			this.now.setTime(n);
			this.nextCheck = this.rc.getNextCheckMillis(this.now);
			try {
				rollOver();
			} catch (IOException ioe) {
				if ((ioe instanceof InterruptedIOException)) {
					Thread.currentThread().interrupt();
				}
				logger.error("rollOver() failed.", ioe);
			}
		}
		super.subAppend(event);
	}

	class RollingCalendar extends GregorianCalendar {
		private static final long serialVersionUID = -3560331770601814177L;
		int type = -1;
		int allHours = 12;
		RollingCalendar() {
		}

		RollingCalendar(TimeZone tz, Locale locale) {
			super(tz, locale);
		}

		void setType(int type) {
			this.type = type;
		}

		public long getNextCheckMillis(Date now) {
			return getNextCheckDate(now).getTime();
		}

		public Date getNextCheckDate(Date now) {
			setTime(now);
			switch (this.type) {
			case 0:
				set(13, 0);
				set(14, 0);
				add(12, 1);
				break;
			case 1:
				set(12, 0);
				set(13, 0);
				set(14, 0);
				add(11, 1);
				break;
			case 2:
				set(12, 0);
				set(13, 0);
				set(14, 0);
				int hour = get(11);
				if (hour < allHours) {
					set(11, 12);
				} else {
					set(11, 0);
					add(5, 1);
				}
				break;
			case 3:
				set(11, 0);
				set(12, 0);
				set(13, 0);
				set(14, 0);
				add(5, 1);
				break;
			case 4:
				set(7, getFirstDayOfWeek());
				set(11, 0);
				set(12, 0);
				set(13, 0);
				set(14, 0);
				add(3, 1);
				break;
			case 5:
				set(5, 1);
				set(11, 0);
				set(12, 0);
				set(13, 0);
				set(14, 0);
				add(2, 1);
				break;
			default:
				throw new IllegalStateException("Unknown periodicity type.");
			}
			return getTime();
		}
	}

	/**
	 * 获取离当天最近maxBackupIndex天的日期范围.如今天是2月27，maxBackupIndex=3，则返回2月25、2月26的日期信息.
	 * @return List<String>
	 */
	List<String> getMaxBackupIndexDates() {
		List<String> result = new ArrayList<String>();
		if (this.maxBackupIndex > 0) {
			for (int i = 1; i < this.maxBackupIndex; i++) {
				Calendar calendar = Calendar.getInstance();
				calendar.setTime(this.now);
				calendar.set(11, 0);
				calendar.set(12, 0);
				calendar.set(13, 0);
				calendar.set(14, 0);
				calendar.add(5, -i);
				result.add(this.sdf.format(calendar.getTime()));
			}
		}
		return result;
	}

	public int getmaxBackupIndex() {
		return this.maxBackupIndex;
	}

	public void setmaxBackupIndex(int maxBackupIndex) {
		this.maxBackupIndex = maxBackupIndex;
	}
	
}